#pragma once
#ifndef SIMPLEMATRIX
#define SIMPLEMATRIX

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include "time.h"
#include <cstdlib>

using namespace std;

template <class A>
class SimpleMatrix
{
public:
	SimpleMatrix(int matrixRows, int matrixColumns, A init)
	{
		rows = (matrixRows > 0 ? matrixRows : 10);
		columns = (matrixColumns > 0 ? matrixColumns : 10);

		matrix.resize(rows * columns);

		for (int i = 0; i < matrix.size(); i++)
			matrix[i] = init;
	}

	SimpleMatrix(int matrixRows, int matrixColumns)
	{
		rows = (matrixRows > 0 ? matrixRows : 10);
		columns = (matrixColumns > 0 ? matrixColumns : 10);

		matrix.resize(rows * columns);
	}
	
	SimpleMatrix(A *elements, int length, int matrixColumns)
	{
		rows = length / matrixColumns;
		columns = matrixColumns;
		matrix.resize(rows * columns);

		for (int i = 0; i < length; i++)
			matrix[i] = elements[i];
	}

	SimpleMatrix(string path, char separator, int row_count, int column_count)
	{
		buildMatrixFromFile(path,separator,row_count,column_count);
	}

	SimpleMatrix(string path,int row_count, int column_count)
	{
		buildMatrixFromFile(path,' ',row_count,column_count);
	}

	SimpleMatrix(string path, char separator)
	{

		getMatrixDimensionFromFile(string path, char separator)
	}

	SimpleMatrix(string path)
	{
		getMatrixDimensionFromFile(path,' ');
	}

	~SimpleMatrix() 
	{
		matrix.clear();
	}

	void print()
	{
		for (int i = 0; i < matrix.size(); i++)
		{
			if (i % columns == 0)
				cout << "\n";

			cout << matrix[i] << (sizeof(A) == sizeof(char) || sizeof(A) == sizeof(unsigned char) ? " " : "\t");
		}

		cout << endl;
	}

	void printIndices()
	{
		for (int i = 0; i < matrix.size(); i++)
		{
			if (i % columns == 0)
				cout << "\n";

			cout << (int)(matrix[i]) << (sizeof(A) == sizeof(char) || sizeof(A) == sizeof(unsigned char) ? " " : "\t");
		}

		cout << endl;
	}

	
	void print(vector<char> symbols, vector<A> elements)
	{
		for (int i = 0; i < matrix.size(); i++)
		{
			if (i % columns == 0)
				cout << "\n";
			int elementIndex = findIndex(elements, matrix[i]);
			if (elementIndex == -1)
				cout << matrix[i] << (sizeof(A) == sizeof(char) || sizeof(A) == sizeof(unsigned char) ? " " : "\t");
			else
				cout << symbols[elementIndex] << " ";
		}

		cout << endl;
	}

	void randomize()
	{
		srand(time(0));

		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
				set(i, j, (A)(rand() % 50));
	}

	
	int height()
	{
		return rows;
	}

	int width()
	{
		return columns;
	}

	void set(int rowIndex, int columnIndex, A value)
	{
		if (rowIndex < 0 || rowIndex >= rows)
		{
			cerr << "\nError in SET row: " << rowIndex << " out of range. Max rows: " << rows;
			exit(1);
		}

		if (columnIndex < 0 || columnIndex >= columns)
		{
			cerr << "\nError in SET column: " << columnIndex << " out of range. Max columns: " << columns;
			exit(1);
		}

		matrix[rowIndex * columns + columnIndex] = value;
	}

	A get(int rowIndex, int columnIndex)
	{
		if (rowIndex < 0 || rowIndex >= rows)
		{
			cerr << "\nError in GET row: " << rowIndex << " out of range. Max rows: " << rows;
			exit(1);
		}

		if (columnIndex < 0 || columnIndex >= columns)
		{
			cerr << "\nError in GET column: " << columnIndex << " out of range. Max columns: " << columns;
			exit(1);
		}

		return matrix[rowIndex * columns + columnIndex];
	};

	vector<A> buffer()
	{
		return matrix;
	}

	vector<A> getRow(int rowIndex)
	{
		if (rowIndex < 0 || rowIndex >= rows)
		{
			cerr << "\nError row: " << rowIndex << " out of range. Max rows: " << rows;
			exit(1);
		}

		vector<A> row(columns);

		for (int i = 0; i < row.size(); i++)
			row[i] = get(rowIndex, i);

		return row;
	}

	void setRow(vector<A> elements, int rowIndex)
	{
		if (rowIndex < 0 || rowIndex >= rows)
		{
			cerr << "\nError row: " << rowIndex << " out of range. Max rows: " << rows;
			exit(1);
		}

		for (int j = 0; j < columns; j++)
			set(rowIndex, j, elements[j]);
	}

	vector<A> getColumn(int columnIndex)
	{
		if (columnIndex < 0 || columnIndex >= columns)
		{
			cerr << "\nError column: " << columnIndex << " out of range. Max columns: " << columns;
			exit(1);
		}

		vector<A> column(rows);
		
		for (int i = 0; i < column.size(); i++)
			column[i] = get(i, columnIndex);

		return column;
	}

	SimpleMatrix<A>* rowSubset(int rowIndex, int offset)
	{
		
		if (rowIndex < 0 || rowIndex > rows)
		{
			cerr << "\nError start row: " << rowIndex<< " out of range. Max rows: " << rows;
			exit(1);
		}

		if (offset <= 0)
		{
			cerr <<"\nError: the offset must be greater than 0";
			exit(1);
		}

		if (rowIndex + offset > rows)
		{
			cerr << "\nError: the offset " << offset << " is too large";
			exit(1);
		}

		SimpleMatrix<A>* subset = new SimpleMatrix<A>(offset, columns);

		for (int i = rowIndex, k = 0; i < rowIndex + offset; i++, k++)
			for (int j = 0; j < columns; j++)
				subset -> set(k, j, get(i, j));

		return subset;
	}

	SimpleMatrix<A>* columnSubset(int columnIndex, int offset)
	{
		
		if (columnIndex < 0 || columnIndex > columns)
		{
			cerr << "\nError start column: " << columnIndex<< " out of range. Max columnss: " << columns;
			exit(1);
		}

		if (offset <= 0)
		{
			cerr <<"\nError: the offset must be greater than 0";
			exit(1);
		}

		if (columnIndex + offset > columns)
		{
			cerr << "\nError: the offset " << offset << " is too large";
			exit(1);
		}

		SimpleMatrix<A>* subset = new SimpleMatrix<A>(rows, offset);

		for (int j = columnIndex, k = 0; j < columnIndex + offset; j++, k++)
			for (int i = 0; i < rows; i++)
				subset -> set(i, k, get(i, j));

		return subset;
	}

	SimpleMatrix<A>* subMatrix(int rowIndex, int columnIndex,int offsetRow, int offsetColumn)
	{
		SimpleMatrix<A> *temp1 = rowSubset(rowIndex,offsetRow);
		SimpleMatrix<A> *sub_matrix = temp1 ->columnSubset(columnIndex,offsetColumn);
		delete temp1;
		return sub_matrix;
	}

	void storeMatrix(string file_name,char separator)
	{
		ofstream output_file(file_name,std::ios::out);
		if (output_file.is_open())
		{
			for (int i = 0; i < rows; i++)
				for (int j = 0; j < columns; j++)
				{
					A x = get(i,j);
					if (j == columns - 1)
						output_file << x << "\n";
					else
						output_file << x << separator;
				}
				output_file.close();

		}
		else
			cout << "Unable to open fine in SimpleMatrix.storeMatrix" << endl;
	}

	void storeMatrix(string file_name)
	{
		storeMatrix(file_name,' ');
	}

	void buildBorder(A x)
	{
		int new_height = rows + 2;
		int new_width = columns + 2;
		SimpleMatrix<A>* border_matrix = new SimpleMatrix<A>(new_height,new_width);
		for (int i = 0,h = 0; i < new_height; i++)
		{
			for (int j = 0,k = 0;j < new_width; j++)
			{
				if (i == 0 || j == 0 || i == new_height - 1 || j == new_width - 1)
					border_matrix ->set(i,j,x);
				else
				{
					border_matrix ->set(i,j,get(h,k));
					k++;
				}
			}

			if (i > 0 && i < new_height - 1)
				h++;
		}

		//copy the border matrix into the current matrix
		matrix.resize(new_height * new_width);
		rows = new_height;
		columns = new_width;
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
				matrix[i * columns + j] = border_matrix ->get(i,j);
		delete border_matrix;
	}


private:
	int rows;
	int columns;
	vector<A> matrix;

	int findIndex(vector<A> list, A element)
	{
		for (int i = 0; i < list.size(); i++)
			if (list[i] == element)
				return i;

		return -1;
	}

	void buildMatrixFromFile(string path, char separator, int row_count, int column_count)
	{
		string line;
		ifstream input_file(path);
		int i = 0;
		int j = 0;
		int k = 0;
		rows = row_count;
		columns = column_count;
		matrix.resize(rows * columns);

		if (input_file.is_open())
		{
			while (input_file.good() && i < row_count)
			{
				getline(input_file,line);
				k = 0;
				j = 0;
				while (k < line.length() && j < column_count)
				{
					if (line[k] != separator)
					{
						A x = (A)(line[k]);
						matrix[i * columns + j] = x;
						j++;
					}
					k++;
				
				}
				i++;
			}
		}
		else
			cout << "Unable to open file in SimpleMatrix constructor" << endl;
	}

	void getMatrixDimensionFromFile(string path, char separator)
	{
		string line;
		ifstream input_file(path);
		int rows = 0;
		int columns = 0;
		int k = 0;
		if (input_file.is_open())
		{
			while (input_file.good())
			{
				getline(input_file,line);
				k = 0;
				columns = 0;
				while (k < line.length())
				{
					if (line[k] != separator)
						columns++;
					k++;
				}
				rows++;
			}
			buildMatrixFromFile(path,separator,rows,columns);
		}
		else
			cout << "Unable to open file in SimpleMatrix constructor" << endl;
	}
};

#endif